from typing import Any
import httpx
from fastmcp import FastMCP

import logging
from logging.handlers import RotatingFileHandler 

import asyncio


# 创建Logger实例
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)  # 设置日志级别

# 文件处理器（自动轮转）
file_handler = RotatingFileHandler(
    'app.log', 
    maxBytes=5*1024*1024,  # 5MB
    backupCount=3,
    encoding='utf-8'
)

file_handler.setFormatter(logging.Formatter(
    '%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
))

# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)  # 控制台只显示WARNING以上

logger.addHandler(file_handler)
logger.addHandler(console_handler)


# 1. 初始化 FaskMCP 服务器
# 创建一个名为 weather 的服务器实例。
mcp = FastMCP("weather")
# --------------- 常量定义 -------------------
# 美国气象局天气API的基础URL
NWS_API_BASE_URL =  "https://api.weather.gov"
# 设置请求头中的User-Agent
USER_AGENT = "weather-app/1.0"

# 辅助函数
async def make_nws_request(url: str) -> dict[str, Any]|None:
    """
    一个通用的异步函数， 用于向NWS API 发送请求， 并处理常见异常
    Args:
        url(str):要请求的完整url
    Returns:
        dict[str, Any]|None 返回解析后的字典 
    """

    headers = {
        "User-Agent": USER_AGENT,
        "Accept": "application/geo+json" # NWS Api 推荐的请求头
    }

    # 使用 httpx.AsyncClient 来执行异步http请求
    async with httpx.AsyncClient() as client:
        try:
            # 发起请求， 设置30s超时
            response = await client.get(url, headers=headers, timeout=30)
            # 如果状态码时4xx 或 5xx , 抛出异常
            response.raise_for_status()
            # 请求成功返回JSON响应体
            return response.json()
        except Exception as e:
            # 捕获异常， 返回None
            logger.warning(f"请求异常: {e}")
            return None 

def format_alert(feature: dict) -> str:
    """
    将天气预警的JSON格式数据转化为人类可读的字符串
    """

    props = feature["properties"]
    # 使用 get方法安全访问字典的键， 如果不存在返回默认值，避免程序出错
    
    return f"""
    事件:{props.get("event", "未知")}
    区域: {props.get('areaDesc', '未知')}
    严重性: {props.get('severity', '未知')}
    描述: {props.get('description', '无描述信息')}
    指令: {props.get('instruction', '无具体指令')}
    """

# --- MCP 工具定义 ------
@mcp.tool()
async def get_alters(state:str) -> str:
    """
    获取美国某个州当前生效的天气预警信息。
    这个函数被 @mcp.tool() 装饰器标记，意味着它可以被大模型作为工具来调用。
   
    Args:
        state: 两个字母的美国州代码 (例如: CA, NY)。
    Returns: 
         州对应的预警信息
    """

    # 构造请求特定州天气预警的url
    url = f"{NWS_API_BASE_URL}/alerts/active/area/{state}"
    logger.info(f"url:{url}")
    data = await make_nws_request(url)

    logger.info(f"预警信息：{data}")
    if not data or "feature" not in data:
        return "该州当前没有生效的天气预警。"
  
    alters = [format_alert(feature) for feature in data["feature"]]
    return "\n---\n".join(alters)

@mcp.tool()
async def get_forecast(latitude: float, longitude: float, days: int = 1):
    """
    根据给定的经纬度获取天气预报，
    最多获取包含当天的6天的天气预报信息， 
    超过6天， 按6天返回。
    同样，这个函数也是一个可被调用的 MCP 工具。

    Args: 
        latitude: 地点的纬度
        longitude: 地点的经度
        days:最近几天的数据 
    Returns: 
        最近的天气预报信息，最多6天
    """

    points_url = f"{NWS_API_BASE_URL}/points/{latitude},{longitude}"
    logger.info(f"points_url:{points_url}")
    points_data = await make_nws_request(points_url)
    
    if not points_data:
        return "无法获取该地点的天气预报数据。"
    
    logger.info(f"经纬度地点信息：{points_data}")
    # 第二步：从上一步的响应中获取实际的天气预报接口的URL
    forecast_url = points_data["properties"]["forecast"]
    # 第三步： 请求详细的天气预报数据
    forecast_data = await make_nws_request(forecast_url)
    logger.info(f"天气预报信息：{forecast_data}")
    if not forecast_data:
        return "无法获取详细的预报信息。"
    logger.info(f"天气预报详情数据：{forecast_data}")
    # 天气预报API最多返回6天天气信息， 每天的天气信息分为白天和晚上。
    number = days * 2
    if number > 12:
        number = 12
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:number]:
        forecast = f"""
        {period['name']}:
        温度: {period['temperature']}°{period['temperatureUnit']}
        风力: {period['windSpeed']} {period['windDirection']}
        预报: {period['detailedForecast']}
        """
        forecasts.append(forecast)
    return "\n---\n".join(forecasts)
# 测试验证
# async def get_alter_test():
#     data = await get_alters("NY")
#     print(f"预警数据：{data}")

# async def get_forecat_test(): 
#     data = await get_forecast(42.6512,-73.755, 2)
#     print(f"天气预报信息:{data}")


if __name__ == '__main__':
    # print("---查询天气预警信息---")
    # asyncio.run(get_alter_test())
    # print("---查询天气预报信息---")
    # asyncio.run(get_forecat_test())
    # 初始化并运行 MCP 服务器
    # transport='stdio' 表示服务器将通过标准输入/输出(stdin/stdout)与客户端（如大模型）进行通信。
    # 这是与本地模型或调试工具交互的常见方式。
    mcp.run(transport='stdio')
